/*******************************************************************************
 *      CSE 531 – Distributed and Multiprocessor Operating Systems
 *
 *           Something of a scheduler for our fake threads.
 *
 *   Author: Denis Ryndine
 *   Email:  dryndine@asu.edu
 *   Date:   2010-11-06
 *
 *   531-scheduler.c: 
 *
 *
 ******************************************************************************/

#include "531-scheduler.h"
#include <ucontext.h>
#include <stdlib.h>
#include <unistd.h>
#include <time.h>
#include <signal.h>
#include <errno.h>
#include <stdio.h>
#include <assert.h>

/*System clock id that will be used by the scheduler's timer.*/
#define SCH531_TIMER_CLOCK_ID     CLOCK_MONOTONIC //CLOCK_PROCESS_CPUTIME_ID

/*Time's up, put next thread on.*/
#define SCH531_SLICE_SIGNAL_ID        SIGRTMIN
/*Thread is blocked on something (like semaphore q), and
wants to be switched.*/
#define SCH531_THR_BLOCKED_SIGNAL_ID  SIGRTMIN+1
/*Thread called sleep, go next.*/
#define SCH531_THR_SLEEP_SIGNAL_ID    SIGRTMIN+2

/*Time slice for each thread to run (in milliseconds).*/
#define SCH531_TIME_SLICE_MS  200


/*These calls must be implemented by the threads part of the project. 
(Suggestion: implement them in your C file, and linker will find them)*/
/*---------------------------------------------------------------------------*/
/* Returns the first context from the run Q (from head).*/
extern ucontext_t* threads_get_first_context_run_Q( void );

/* Move to next thread in run Q(=rotate Q), and return its context. The
current running thread's context will be swapped for returned one, so this is
the pre-emption.*/
extern ucontext_t* threads_rotate_context_run_Q( void );

/* A blocked thread will have removed itself from run Q, and then raised the
'blocked' signal (by calling sch531_thread_blocked()) [ e.g. call on semaphore
P() could have removed the thread from run Q ]. The call below allows for 
the blocked thread's context saved before switching to next thread. 
Note: before manipulating runQ the thread must lock it with by calling 
sch531_lock_context(). See example in sch531_test.c */
extern ucontext_t* threads_get_blocked_context( void );

/* *** NOTE: because the above calls will be called from signal handlers, 
 they must be 'safe'. Don't do anything heavy in them.*/
/*---------------------------------------------------------------------------*/


/* ------------- Privates ----------------------------------------------------*/

static timer_t _sch531_timerid;

//Thread is in 'critical section' flag
static sig_atomic_t _sch531_thr_in_cs=0;
/* ----------------------------------------------------------------------*/

static int
_make_timer(void)
{
  struct sigevent sev;
  int error=0;

  sev.sigev_notify = SIGEV_SIGNAL;
  sev.sigev_signo  = SCH531_SLICE_SIGNAL_ID;
  sev.sigev_value.sival_ptr = &_sch531_timerid;
  
  if (0 != timer_create(SCH531_TIMER_CLOCK_ID, &sev, &_sch531_timerid))
  {
    error = errno;
    perror("Failed to timer_create");
  }
  return error;
}

static int
_setup_timer(void)
{
  struct itimerspec its = 
   { 
     {0, SCH531_TIME_SLICE_MS*1000000}, 
     {0, SCH531_TIME_SLICE_MS*1000000}
   };

  int error = 0;
  if (0 != timer_settime(_sch531_timerid, 0, &its, NULL))
  {
    error = errno; 
    perror("Failed to timer_settime");
  }
  return error;
}

static void 
_sch531_signal_handler(int sig, siginfo_t *si, void* uc)
{
  /*Notes:
     This is the signal handler callback that makes things tick.
     
  */
  ucontext_t* curr_cnxt = threads_get_first_context_run_Q();
  
  /*If this was time tick or if thread blocked on sleeping..*/
  if (SCH531_SLICE_SIGNAL_ID == sig || SCH531_THR_SLEEP_SIGNAL_ID == sig)
  {
    //Is it in critical section?
    if(!_sch531_thr_in_cs)
    {
      ucontext_t* next_cnxt = threads_rotate_context_run_Q();
      if (next_cnxt && curr_cnxt && next_cnxt != curr_cnxt)
      {
        if (0 != swapcontext(curr_cnxt, next_cnxt))
        {
          perror("sch531: failed to swapcontext");
        }
      }
    }
  }
  else
  if(SCH531_THR_BLOCKED_SIGNAL_ID == sig)
  {
    ucontext_t* blkd_cntxt = threads_get_blocked_context();
    if (curr_cnxt && blkd_cntxt)
    {
      //Release csection
      _sch531_thr_in_cs=0;    
      if(0 != swapcontext(blkd_cntxt, curr_cnxt))
      {
        perror("sch531: failed to setcontext");
      }
    }
  }
  else
  if (SIGINT ==sig)
  {
    printf("-- Caught interrupt signal, aborting.\n");
    exit(2);
  }
}

static int
_install_sighandler(void)
{
  int error = 0;
  struct sigaction sa_new;
  
  sa_new.sa_flags       = SA_SIGINFO; 
  sa_new.sa_sigaction   = _sch531_signal_handler;
  
  sigemptyset(&sa_new.sa_mask);
  sigaddset(&sa_new.sa_mask, SCH531_THR_SLEEP_SIGNAL_ID);
  sigaddset(&sa_new.sa_mask, SCH531_THR_BLOCKED_SIGNAL_ID);
  sigaddset(&sa_new.sa_mask, SCH531_SLICE_SIGNAL_ID);
  if (0 != sigaction(SCH531_SLICE_SIGNAL_ID, &sa_new, NULL))
  {
    error = errno;
    perror("Failed to sigaction");
  }
  //sigemptyset(&sa_new.sa_mask);
  //sigdelset(&sa_new.sa_mask, SCH531_THR_SLEEP_SIGNAL_ID);
  //sigaddset(&sa_new.sa_mask, SCH531_SLICE_SIGNAL_ID);
  if (0 != sigaction(SCH531_THR_SLEEP_SIGNAL_ID, &sa_new, NULL))
  {
    error = errno;
    perror("Failed to sigaction");
  }
  //sigdelset(&sa_new.sa_mask, SCH531_THR_BLOCKED_SIGNAL_ID);
  if (0 != sigaction(SCH531_THR_BLOCKED_SIGNAL_ID, &sa_new, NULL))
  {
    error = errno;
    perror("Failed to sigaction");
  } 
  sigemptyset(&sa_new.sa_mask);
  sigaction(SIGINT, &sa_new, NULL);
  return error;
}


/* -------------------- Publics ----------------------------------------------*/

int
sch531_init(void)
{
  int err=0;

  if ((err=_make_timer()))
    return err;
  if ((err=_install_sighandler()))
    return err;    
  if ((err=_setup_timer()))
    return err;
  //block signals until usr unblocks us
  if ((err=sch531_block()))
    return err;

  return 0;
}

void 
sch531_run(void)
{
  //Start first thread made. Note we never return to current context
  ucontext_t* cnxt = threads_get_first_context_run_Q();
  
  if (!cnxt)
  {
    fprintf(stderr, "sch531_run: is your run q empty??\n");
    return;
  }
  if (0 != sch531_unblock())
  {
    fprintf(stderr, "sch531_run: failed to unblock scheduler. Bail...\n");
    return;
  }  
  if (0 != setcontext(cnxt))
  {
    perror("sch531_run(): failed to setconext");
  }
}


int
sch531_block(void)
{
  sigset_t mask;
  sigemptyset(&mask);
  sigaddset(&mask, SCH531_SLICE_SIGNAL_ID);
  int err=0;
  
  if ((err=sigprocmask(SIG_BLOCK, &mask, NULL)))
  {
    perror("Failed to sigprocmask");
  }
  return err;
}

int
sch531_unblock(void)
{
  sigset_t mask;
  sigemptyset(&mask);
  sigaddset(&mask, SCH531_SLICE_SIGNAL_ID);
  int err=0;

  if ((err=sigprocmask(SIG_UNBLOCK, &mask, NULL)))
  {
    perror("Failed to sigprocmask");
    return err;
  }
    
  return 0;
}

inline int 
sch531_lock_context(void)
{
  _sch531_thr_in_cs=1;
  return 0;
}

/*Unlock context / thread may be preempted.*/
inline int 
sch531_unlock_context(void)
{
  _sch531_thr_in_cs=0;
  return 0;
}

int 
sch531_thread_sleep(unsigned secs)
{
  struct timespec t0, t1;
  int err=0;
  
  if ((err=clock_gettime(SCH531_TIMER_CLOCK_ID, &t0)))
  {
    perror("clock_gettime error");
    return err;
  }
  do 
  {
    //pause();
    //ask to be swapped out
    if (0 != raise(SCH531_THR_SLEEP_SIGNAL_ID))
    {     
      perror("raise error");
    }
    if ((err=clock_gettime(SCH531_TIMER_CLOCK_ID, &t1)))
    {
      perror("clock_gettime error");
      break;
    }
  }while( t1.tv_sec - t0.tv_sec < secs);  
  
  return err;
}


inline int 
sch531_thread_yield(void)
{
  return raise(SCH531_SLICE_SIGNAL_ID);
}
inline int
sch531_thread_blocked(void)
{
  return raise(SCH531_THR_BLOCKED_SIGNAL_ID);
}


/*****************************   EOF    *************************************/
